package com.opensky.qatest.util.selenium;

import java.util.List;
import java.util.Set;

import junit.framework.Assert;

import org.apache.log4j.Logger;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keys;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.Wait;
import org.openqa.selenium.support.ui.WebDriverWait;

import com.opensky.qatest.util.common.Properties;

/*
 * WebDriverWrapper - wraps standard WebDriver methods
 * and adds external fixtures: such as wait for element present.
 * 
 *
 * @author: Aliakse_Khursevich (hursevich@gmail.com)
 * @version: 1.1
 * 
 */
public class WebDriverWrapper implements WebDriver, TakesScreenshot {

    private static final Logger LOGGER = Logger.getLogger(WebDriverWrapper.class);

    // wait for element present for 25 second
    private static final long MAX_TIMEOUT = Properties.getInt("selenium.max.timeout");

    // and retry checking every 1 second
    private static final long RETRY_TIME = Properties.getInt("selenium.retry.time");

    private long timer;

    private Set<String> beforeHandles;

    static Wait<WebDriver> wait;

    private WebDriver driver;

    private Report report;

    public WebDriverWrapper(WebDriver driver) {
	this.driver = driver;
	report = new Report(driver);
    }

    // ****************** Base wait before act methods ********************

    public boolean waitAndVerifyElementPresent(String name, final By by) {

	wait = new WebDriverWait(driver, MAX_TIMEOUT, RETRY_TIME * 1000);
	try {
	    wait.until(new ExpectedCondition<Boolean>() {
		public Boolean apply(WebDriver dr) {
		    return dr.findElement(by) != null;
		}
	    });
	} catch (Exception e) {
	    report.logElementNotPresent(name);
	    return false;
	}
	report.logElementPresent(name);
	return true;
    }

    public boolean waitAndAssertElementPresent(String name, final By by) {

	wait = new WebDriverWait(driver, MAX_TIMEOUT, RETRY_TIME * 1000);
	try {
	    wait.until(new ExpectedCondition<Boolean>() {
		public Boolean apply(WebDriver dr) {
		    return dr.findElement(by) != null;
		}
	    });
	} catch (Exception e) {
	    Assert.fail(report.logElementNotPresent(name));
	}
	report.logElementPresent(name);
	return true;
    }

    public boolean waitTimeAndVerifyElementPresent(String name, final By by, long timeout) {

	wait = new WebDriverWait(driver, timeout, RETRY_TIME * 1000);
	try {
	    wait.until(new ExpectedCondition<Boolean>() {
		public Boolean apply(WebDriver dr) {
		    return dr.findElement(by) != null;
		}
	    });
	} catch (Exception e) {
	    return false;
	}
	return true;
    }

    public boolean waitTimeAndAssertElementPresent(String name, final By by, long timeout) {

	wait = new WebDriverWait(driver, timeout, RETRY_TIME * 1000);
	try {
	    wait.until(new ExpectedCondition<Boolean>() {
		public Boolean apply(WebDriver dr) {
		    return dr.findElement(by) != null;
		}
	    });
	} catch (Exception e) {
	    Assert.fail(report.logElementNotPresent(name));
	}
	report.logElementPresent(name);
	return true;
    }

    public boolean waitAndVerifyElementNotPresent(String name, final By by) {

	wait = new WebDriverWait(driver, MAX_TIMEOUT, RETRY_TIME * 1000);
	try {
	    wait.until(new ExpectedCondition<Boolean>() {
		public Boolean apply(WebDriver dr) {
		    return dr.findElements(by).size() == 0;
		}
	    });
	} catch (Exception e) {
	    report.logUnexpectedElementPresent(name);
	    return false;
	}
	return true;
    }

    public boolean waitAndAssertElementNotPresent(String name, final By by) {

	wait = new WebDriverWait(driver, MAX_TIMEOUT, RETRY_TIME * 1000);
	try {
	    wait.until(new ExpectedCondition<Boolean>() {
		public Boolean apply(WebDriver dr) {
		    return dr.findElements(by).size() == 0;
		}
	    });
	} catch (Exception e) {
	    Assert.fail(report.logUnexpectedElementPresent(name));
	}
	return true;
    }

    public WebElement waitAssertAndGetElement(String name, final By by) {

	wait = new WebDriverWait(driver, MAX_TIMEOUT, RETRY_TIME * 1000);
	try {
	    wait.until(new ExpectedCondition<Boolean>() {
		public Boolean apply(WebDriver dr) {
		    return dr.findElement(by) != null;
		}
	    });
	} catch (Exception e) {
	    Assert.fail(report.logElementNotPresent(name));
	}
	report.logElementPresent(name);
	return driver.findElement(by);
    }

    public WebElement waitVerifyAndGetElement(String name, final By by) {

	wait = new WebDriverWait(driver, MAX_TIMEOUT, RETRY_TIME * 1000);
	try {
	    wait.until(new ExpectedCondition<Boolean>() {
		public Boolean apply(WebDriver dr) {
		    return dr.findElement(by) != null;
		}
	    });
	} catch (Exception e) {
	    report.logElementNotPresent(name);
	    return null;
	}
	report.logElementPresent(name);
	return driver.findElement(by);
    }

    public List<WebElement> waitVerifyAndGetElements(String name, final By by) {

	wait = new WebDriverWait(driver, MAX_TIMEOUT, RETRY_TIME * 1000);
	try {
	    wait.until(new ExpectedCondition<Boolean>() {
		public Boolean apply(WebDriver dr) {
		    return dr.findElements(by) != null;
		}
	    });
	} catch (Exception e) {
	    report.logElementNotPresent(name);
	    return null;
	}
	report.logElementPresent(name);
	return driver.findElements(by);
    }

    public List<WebElement> waitAssertAndGetElements(String name, final By by) {

	wait = new WebDriverWait(driver, MAX_TIMEOUT, RETRY_TIME * 1000);
	try {
	    wait.until(new ExpectedCondition<Boolean>() {
		public Boolean apply(WebDriver dr) {
		    return dr.findElements(by) != null;
		}
	    });
	} catch (Exception e) {
	    Assert.fail(report.logElementNotPresent(name));
	}
	report.logElementPresent(name);
	return driver.findElements(by);
    }

    public boolean waitTimeAndDefineElementWithTextPresent(String name, final By by, final String text, long timeout) {

	wait = new WebDriverWait(driver, timeout, RETRY_TIME * 1000);
	try {
	    wait.until(new ExpectedCondition<Boolean>() {
		public Boolean apply(WebDriver dr) {
		    return (driver.findElement(by) != null && driver.findElement(by).getText().contains(text));
		}
	    });
	} catch (Exception e) {
	    return false;
	}
	return true;
    }

    public boolean waitAndVerifyElementWithTextPresent(String name, final By by, final String text) {

	final WebElement element = waitVerifyAndGetElement(name, by);

	wait = new WebDriverWait(driver, MAX_TIMEOUT, RETRY_TIME * 1000);
	if (element != null) {
	    try {
		wait.until(new ExpectedCondition<Boolean>() {
		    public Boolean apply(WebDriver dr) {
			return element.getText().contains(text);
		    }
		});
	    } catch (Exception e) {
		report.logElementNotPresent(name);
		return false;
	    }
	    report.logElementWithTextPresent(name, text);
	    return true;
	} else {
	    report.logElementWithTextNotPresent(name, text);
	    return false;
	}
    }

    public boolean waitAndAssertTextPresent(String name, final By by, final String text) {

	final WebElement element = waitVerifyAndGetElement(name, by);

	wait = new WebDriverWait(driver, MAX_TIMEOUT, RETRY_TIME * 1000);
	if (element != null) {
	    try {
		wait.until(new ExpectedCondition<Boolean>() {
		    public Boolean apply(WebDriver dr) {
			return element.getText().contains(text);
		    }
		});
	    } catch (Exception e) {
		Assert.fail(report.logElementWithTextNotPresent(name, text));
		return false;
	    }
	    report.logElementWithTextPresent(name, text);
	    return true;
	} else {
	    report.logElementWithTextNotPresent(name, text);
	    return false;
	}
    }

    public boolean waitAndVerifyTextNotPresent(String name, final By by, String text) {

	WebElement element = waitVerifyAndGetElement(name, by);

	wait = new WebDriverWait(driver, MAX_TIMEOUT, RETRY_TIME * 1000);
	if (element != null && element.getText().contains(text)) {
	    report.logUnexpectedElementWithTextPresent(name, text);
	    return false;

	} else {
	    return true;
	}
    }

    public boolean waitAndAssertTextNotPresent(String name, final By by, String text) {

	WebElement element = waitVerifyAndGetElement(name, by);

	wait = new WebDriverWait(driver, MAX_TIMEOUT, RETRY_TIME * 1000);
	if (element != null && element.getText().contains(text)) {
	    Assert.fail(report.logUnexpectedElementWithTextPresent(name, text));
	    return false;
	} else {

	    return true;
	}
    }

    public void waitAndClick(String name, final By by) {

	WebElement element = waitAssertAndGetElement(name, by);
	clickSafe(element, true);
	report.elementIsClicked(name);
    }

    private void clickSafe(WebElement element, boolean startTimer) {

	if (startTimer) {
	    timer = System.currentTimeMillis();
	}

	try {
	    Thread.sleep(RETRY_TIME * 1000);
	    element.click();

	} catch (Exception e) {
	    if (System.currentTimeMillis() - timer < MAX_TIMEOUT * 1000) {
		clickSafe(element, false);
	    }
	}
    }

    public void waitAndSendEnter(String name, final By by) {
	WebElement element = waitAssertAndGetElement(name, by);
	sendEnterSafe(element, true);
	report.elementSendKeys(name, "Enter");
    }

    private void sendEnterSafe(WebElement element, boolean startTimer) {

	if (startTimer) {
	    timer = System.currentTimeMillis();
	}

	try {
	    Thread.sleep(RETRY_TIME * 1000);
	    element.sendKeys(Keys.ENTER);

	} catch (Exception e) {
	    if (System.currentTimeMillis() - timer < MAX_TIMEOUT * 1000) {
		sendEnterSafe(element, false);
	    }
	}
    }

    public void waitAndSendKeys(String name, final By by, String keys) {

	WebElement element = waitAssertAndGetElement(name, by);
	element.clear();
	element.sendKeys(keys);
	report.elementSendKeys(name, keys);
    }

    public void waitAndHover(String name, final By by) {

	WebElement element = waitAssertAndGetElement(name, by);
	Actions builder = new Actions(driver);
	builder.moveToElement(element).build().perform();
	report.elementIsHovered(name);
    }

    public boolean waitAndCheckURL(final String url) {
	wait = new WebDriverWait(driver, MAX_TIMEOUT, RETRY_TIME * 1000);
	try {
	    wait.until(new ExpectedCondition<Boolean>() {
		public Boolean apply(WebDriver dr) {
		    return driver.getCurrentUrl().contains(url);
		}
	    });
	} catch (Exception e) {
	    Assert.fail(Report.wrongURL(url, driver.getCurrentUrl()));
	}
	return true;
    }

    public boolean waitAndVerifyURL(final String url) {
	wait = new WebDriverWait(driver, MAX_TIMEOUT, RETRY_TIME * 1000);
	try {
	    wait.until(new ExpectedCondition<Boolean>() {
		public Boolean apply(WebDriver dr) {
		    return driver.getCurrentUrl().contains(url);
		}
	    });
	} catch (Exception e) {
	    return false;
	}
	return true;
    }

    public void pause(long timeout) {
	try {
	    Thread.sleep(timeout * 1000);
	} catch (InterruptedException e) {
	    e.printStackTrace();
	}
    }

    public void saveBeforeHandles() {
	beforeHandles = driver.getWindowHandles();
    }

    public void switchToBottomWindow() {
	try {
	    String newWindowHandle = beforeHandles.iterator().next();
	    driver.switchTo().window(newWindowHandle);
	} catch (Exception e) {
	    LOGGER.warn("Switching to bottom window was not performed!");
	}
    }

    public void switchToTopWindow() {
	try {
	    Set<String> afterHandles = driver.getWindowHandles();
	    afterHandles.removeAll(beforeHandles);
	    String newWindowHandle = afterHandles.iterator().next();
	    driver.switchTo().window(newWindowHandle);
	} catch (Exception e) {
	    LOGGER.warn("Switching to top window was not performed!");
	}
    }

    // ****************** Javascript methods ********************

    public void maximizeWindow() {
	if (!(driver instanceof HtmlUnitDriver)) {
	    ((JavascriptExecutor) driver)
		    .executeScript("if (window.screen){window.moveTo(0, 0);window.resizeTo(window.screen.availWidth, window.screen.availHeight);};");
	}
    }

    public void sielentAlert() {
	if (!(driver instanceof HtmlUnitDriver)) {
	    ((JavascriptExecutor) driver).executeScript("window.alert = function(msg) { return true; }");
	    ((JavascriptExecutor) driver).executeScript("window.confirm = function(msg) { return true; }");
	    ((JavascriptExecutor) driver).executeScript("window.prompt = function(msg) { return true; }");
	}
    }

    // ****************** WebDriver implemented methods ********************

    public void close() {
	driver.close();
    }

    public void goBack(int times) {
	for (int i = 0; i < times; i++) {
	    driver.navigate().back();
	}
    }

    public WebElement findElement(By locator) {
	return driver.findElement(locator);
    }

    public List<WebElement> findElements(By locator) {
	return driver.findElements(locator);
    }

    public void get(String url) {
	driver.get(url);
	report.logOpenURL(url);
    }

    public String getCurrentUrl() {
	return driver.getCurrentUrl();
    }

    public String getPageSource() {
	return driver.getPageSource();
    }

    public String getTitle() {
	return driver.getTitle();
    }

    public String getWindowHandle() {
	return driver.getWindowHandle();
    }

    public Set<String> getWindowHandles() {
	return null;
    }

    public Options manage() {
	return driver.manage();
    }

    public Navigation navigate() {
	return driver.navigate();
    }

    public void quit() {
	driver.quit();
    }

    public TargetLocator switchTo() {
	return driver.switchTo();
    }

    public <X> X getScreenshotAs(OutputType<X> outType) throws WebDriverException {
	if (driver instanceof FirefoxDriver) {
	    return ((FirefoxDriver) driver).getScreenshotAs(outType);
	} else if (driver instanceof ChromeDriver) {
	    return ((ChromeDriver) driver).getScreenshotAs(outType);
	} else if (driver instanceof InternetExplorerDriver) {
	    return ((InternetExplorerDriver) driver).getScreenshotAs(outType);
	} else {
	    return null;
	}
    }

    public boolean isAnyValidationFail() {
	return report.isVerificationFailed();
    }
}
